<?php
/**
 * Distributed under the terms of the Eclipse Public License - v 1.0 (see epl-v10.html)
 *
 * If you use this software please consider contributing your revisions or extensions back
 * to the google code project at: 
 *            http://code.google.com/p/data-lotro-com-php-helper-class/
 *
 * Also consider adding an acknowledgement to your website:
 *  "This site uses the data.lotro.com PHP helper class provided by Chris Thomson and Renkosi.com"
 *
 * Please configure the settings in lotroapi.config.php before using this class.
 *
 * Example use:
 *
 *     	require_once("../src/lotroapi.php");
 *
 *     	$kinship = "Knights+of+the+White+Lady";
 *
 *     	$world = "Landroval";
 *
 *     	$player = "Valdidar";
 *
 *     	$itemid = "1879097424";
 *
 *     	$api = new api();
 *
 *     	$c = $api->getCharacterSheet($world, $player); // To fetch a character
 *
 *     	$k = $api->getGuildRoster($world, $kinship); // to fetch a kinship
 *
 *     	$item = $api->getItemInformation($itemid); // to fetch item information
 *
 *
 * Notes:
 *
 * 	For this class to work the chracter to be viewed must be set as public at my.lotro.com
 *
 * Change Log:
 *
 * v0.1 - Alpha release for testing. (CTHOMSON - 14/4/2009)
 *
 * v0.2 - Added separate config file. (CTHOMSON - 16/5/2009)
 *        Read item information from Lorebook tooltips.
 *        Enhanced data classes with some useful functions.
 *        Oh look documentation!
 *        Fixed a problem with utf8 encoded item names
 * v0.3 - Added the option to use Curl to get files, incase the server has fopen diasables for URLs  (CTHOMSON - 17/5/2009).
 *        Please note, this build requires PHP5.
 *        To use CURL it must be compiled into PHP.
 * v0.4 - Added a method to make it easy to retrive specific items
 *        Fixed toString for lorebookItem, where stackSize was not shown
 *        Fixed bug in reading itemWorth in lorebookItem
 *        Fixed a bug in the file cache check
 *
 *
 * @author Chris Thomson <renkosi@smog-it.co.uk>
 * @copyright Copyright (c) 2009, Chris Thomson
 * @see lotroapi.config.php
 * @version 0.4
 */



/**
 * USER CONFIGRABLE SETTINGS.
 * Please edit in the included file
 *
 * @see lotroapi.config.php
 */
require_once("lotroapi.config.php");


////////////////////////////////////////////////////
// Do not modify!
//
//


/**
 * Used to turn HTML to XML
 */
require_once("htmlentities2.php");


/**
 * Used to transform a error message into a code.
 *
 */
class ErrorCodes {

	/**
	 * The table of error messages to error codes.
	 */
	private static $codes = array( "invalidApiKey"=>5001, 
		"Wrong element"=>5002 );

	/**
	 * Used to work out what the error code is for some textual error.
	 *
	 * @param string $code the error message
	 * @return int the error code 
	 */
	public static function getCode($code)
	{
		if (isset(self::$codes[strval($code)])) return self::$codes[strval($code)];
		else return 5000;
	}

}

/**
 * Used to access the data.lotro.com API.
 *
 * @see http://my.lotro.com/bracket/datalotrocom-usage-guide/
 */
class Api {

	/**
	 * Deletes a file from the cache if it is older than DATA_LOTRO_COM_CACHE_TIME
	 * 
	 * @param string $key
	 */
	private function checkFileCache($key)
	{
		if (file_exists(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key") 
			&& filemtime(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key") < time()-(DATA_LOTRO_COM_CACHE_TIME/1000))
			unlink(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key") ;
	}

	/**
	 * Returns a file handle to the specified url
	 * 
	 * @return filehandle of the newly cached file.
	 * @param string $request   The request as a page location.
	 * @param string $key The key to store this request in the cache.
	 */
	private function fetchURL($request, $key)
	{
		if (!DATA_LOTRO_COM_USE_CURL)
		{		

			//fetch file from server.
			$handle = fopen(DATA_LOTRO_COM_SERVER.'/'.DATA_LOTRO_COM_DEVELOPER.'/'.DATA_LOTRO_COM_API_KEY.'/'.$request, "r");
			echo DATA_LOTRO_COM_SERVER.'/'.DATA_LOTRO_COM_DEVELOPER.'/'.DATA_LOTRO_COM_API_KEY.'/'.$request;
			$data = fread($handle,512);
			$file = $data;
			while ($data != "")
			{
				$data = fread($handle,512);	
				$file .= $data;
			}
			fclose($handle); 
		}
		else
		{

			// create curl resource
			$ch = curl_init();

			// set url
			curl_setopt($ch, CURLOPT_URL, DATA_LOTRO_COM_SERVER.'/'.DATA_LOTRO_COM_DEVELOPER.'/'.DATA_LOTRO_COM_API_KEY.'/'.$request);

			//return the transfer as a string
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

			// $output contains the output string
			$file = curl_exec($ch);

			// close curl resource to free up system resources
			curl_close($ch);  
		}

		//write out the cache file.
		$handle = fopen(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key", "w");
		fwrite($handle, $file);
		fclose ($handle);

		//open the new file for reading.
		return fopen(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key", "r");


	}

	/**
	 * returns a filehandle if the request is cached, otherwise null.
	 * 
	 * @return filehandle
	 * @param string $key
	 */
	private function checkCache($key)
	{
		$this->checkFileCache($key);
		if (file_exists(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key"))
		{
			return fopen(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key", "r");
		}
		else
			return null;
	}

	/**
	 * Returns a file handle to the specified url
	 * 
	 * @return filehandle of the newly cached file.
	 * @param object $request   The request as a page location.
	 */
	private function fetchURL1($request, $key)
	{
		if (!DATA_LOTRO_COM_USE_CURL)
		{
			//fetch file from server.
			$handle = fopen($request, "r");
			$data = fread($handle,512);
			$file = $data;
			while ($data != "")
			{
				$data = fread($handle,512);	
				$file .= $data;
			}
			fclose($handle); 
		}
		else
		{
			// create curl resource
			$ch = curl_init();

			// set url
			curl_setopt($ch, CURLOPT_URL, $request);

			//return the transfer as a string
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

			// $output contains the output string
			$file = curl_exec($ch);

			// close curl resource to free up system resources
			curl_close($ch);  
			//write out the cache file.
		}

		$handle = fopen(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key", "w");
		fwrite($handle, $file);
		fclose ($handle);

		//open the new file for reading.
		return fopen(DATA_LOTRO_COM_CACHE_DIRECTOY."/$key", "r");

	}

	/**
	 * Used to fetch a lorebook item using the AJAX method.
	 * It returns a small fragment of HTML that represents the item, you will need to parse this!
	 *
	 *
	 * @param int id the id of the item to find
	 * @return string
	 */
	private function getLorebookRequest($id)
	{
		$key = 'lb_'.$id;
		$request = 'http://lorebook.lotro.com/index.php?action=ajax&rs=efLotroResourceAjaxWrapper&rsargs[]=item&rsargs[]='.$id;
		$handle = $this->checkCache($key);
		if ($handle == null)
			$handle = $this->fetchURL1($request, $key);

		$data = fread($handle,512);
		$file = $data;
		while ($data != "")
		{
			$data = fread($handle,512);	
			$file .= $data;
		}
		fclose($handle);

		return $file;
	}

	/**
	 * Use to get a file handle to a request. The key provided caches the request for DATA_LOTRO_COM_CACHE_TIME.
	 * 
	 * @return string the result of the request.
	 * @param string $key		A unique key to represent this request
	 * @param string $request   The request as a page location.
	 */
	private function getRequest($key, $request)
	{
		$handle = $this->checkCache($key);
		if ($handle == null)
			$handle = $this->fetchURL($request, $key);

		$data = fread($handle,512);
		$file = $data;
		while ($data != "")
		{
			$data = fread($handle,512);	
			$file .= $data;
		}
		fclose($handle);

		return $file;
	}

	/**
	 * Returns various public information about a character - this works for both freepeople and monsters. The
	 * XML structure is similar between them, but monsters have far less data accessible at this time.
	 * 	 
	 * @param string $worldName world name 		
	 * @param string $characterName character name  
	 * @return Character
	 * @see http://my.lotro.com/bracket/datalotrocom-usage-guide/
	 */
	public function getCharacterSheet($worldName, $characterName)
	{
		$key=urlencode("cs_".$worldName."_".$characterName);
		$request = "charactersheet/w/$worldName/c/$characterName/";

		$xml = $this->getRequest($key,$request);

		$xmlElement = simplexml_load_string($xml);

		if ($xmlElement->getName() != "apiresponse")
			throw new Exception("API call failed.", ErrorCodes::getCode("Unknown"));
		else
		{
			$c = $xmlElement->children();
			return new Character($c[0]);
		}
	}

	/**
	 * Returns basic information about the kinship or tribe as well as a basic roster.  
	 * There are no substantial differences between a Free People guild (kinship) or a monster guild (tribe).
	 * 
	 * @param string $worldName world name
	 * @param string $kinship kinship/tribe name
	 * @return Guild
	 * @see http://my.lotro.com/bracket/datalotrocom-usage-guide/
	 */
	public function getGuildRoster($worldName, $kinship)
	{
		$key=urlencode("gr_".$worldName."_".$kinship);
		$request = "guildroster/w/$worldName/g/$kinship/";
		$xml = $this->getRequest($key,$request);

		$xmlElement = simplexml_load_string($xml);

		if ($xmlElement->getName() != "apiresponse")
			throw new Exception("API call failed.", ErrorCodes::getCode("Unknown"));
		else
		{
			$c = $xmlElement->children();
			return new Guild($c[0]);
		}
	}


	/**
	 * Helper function to find a HTML element by class name.
	 *
	 * @since 0.2
	 * @author C Thomson
	 * @param SimpleXMLElement $xml the html structure to check
	 * @param string $class the name of the class to find.
	 * @return SimpleXMLElement
	 */
	private function getHTMLClass($xml, $class)
	{
		foreach ($xml->children() as $child) {
			if ($child['class'] == $class)
				return $child;
			$e = $this->getHTMLClass($child, $class);
			if ($e != null)
				return $e;

		}
		return null;
	}


	/**
	 *
	 *  Uses the Ajax API to get the properties of items from the lorebook. This method is SLOW
	 *  For each item a new request is made, although the data is cached. In addition the data 
	 *  is returned in a unuseful format, so it needs to be parsed.
	 *
	 * The method may fail (gracefully) if the html returned by the lorebook changes.
	 *
	 * @param int $itemid the id number of the item to lookup.
	 * @return LorebookItem
	 * @see http://my.lotro.com/sapience/2009/04/16/how-to-enable-lorebook-tooltips-on-your-site/
	 * @since 0.2
	 */
	public function getItemInformation($itemid)
	{
		$xml = $this->getLorebookRequest($itemid);

		$xmlElement = simplexml_load_string(replaceHtmlEntities($xml));
		if (!$xmlElement)
			throw new Exception("Expected lorebook entry ($itemid), found ".$xml, ErrorCodes::getCode("Lorebook request failed"));

		$r = $this->getHTMLClass($xmlElement, 'tooltipbody');

		if ($r == null)
			throw new Exception("Lorebook Item call failed.", ErrorCodes::getCode("Unknown"));
		return new LorebookItem($r);

	}


}




//////////////////////////////////
// Data classes to represent XMl data follow.

/**
 * Represents a lorebook item.
 *
 * @since 0.2
 */ 
class LorebookItem{
	/** 
	 * The url of the icon of this item 
	 *
	 * @var string
	 */
	public $icon;
	/** 
	 * The name of this item.
	 *
	 * @var string
	 */	
	public $itemname; 
	/** 
	 * How common is this item, e.g. "common", "uncommon" 
	 * 
	 * @var string
	 */
	public $rareity; 
	/**
	 * The type of this item (slot)
	 *
	 * @var string
	 */
	public $type;
	/**
	 * An array of the the requirements for this item.
	 *
	 * @var array
	 */
	public $requirements; // contains itemrequirement(s)
	/**
	 * The description of the item.
	 *
	 * @var string
	 */
	public $description;
	/** 
	 * How many items of this type can be stacked.
	 *
	 * @var int
	 */
	public $stackSize;
	/**
	 * How much is this item worth. In the format G,S,C
	 *
	 * @var string
	 */
	public $worth;
	/**
	 * How sturdy is this item?
	 *
	 * @var string
	 */
	public $sturdiness;
	/**
	 * How worn is this item? format N/N
	 *
	 * @var string
	 */
	public $durability;
	/**
	 * The properties of this item, as strings.
	 *
	 * @var array
	 */
	public $properties;


	/**
	 * Coverts this object to a string. Usefult to check what details were parsed.
	 *
	 * @return string
	 */
	public function toString()
	{
		$ret =  "Icon: $this->icon, Name: $this->itemname, Rareity: $this->rareity, Type: $this->type, Requirements: '";

		foreach ($this->requirements as $r)
			$ret .= $r." ";

		$ret .="', Properties: '";

		foreach ($this->properties as $r)
			$ret .= $r." ";
		$ret .= "', Description: $this->description, Stack size: $this->stackSize, Worth: $this->worth, Durability: $this->durability, Studiness: $this->sturdiness\n";

		return $ret;
	}

	/**
	 * Gets the silver coin component of worth.
	 *
	 * @return int
	 */
	public function getSilverCoin()
	{
		$m = (split(',',$worth));
		return $m[1];
	}

	/**
	 * Gets the gold coin component of worth.
	 *
	 * @return int
	 */
	public function getGoldCoin()
	{
		$m =(split(',',$worth));
		return $m[0];
	}

	/**
	 * Gets the copper coin component of worth.
	 *
	 * @return int
	 */
	public function getCopperCoin()
	{
		$m = (split(',',$worth));
		return $m[2];
	}

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {


		foreach ($xml->children() as $child) {
			switch ($child['class']) {
			case 'itemicon':  //<img class="itemicon" src="http://content.level3.turbine.com/sites/lorebook.lotro.com/images/icons/item/ingredient/it_craft_short_black_ash_shaft.png" />
				$this->icon = $child['src'];
				break;
			case 'itemtype': //<div class="itemtype">Ingredient&nbsp;</div>
				$this->type = trim(html_entity_decode((string)$child));
				break;	
			case 'itemreq': //<div class="itemreq"> <div class="itemrequirement">Minimum Level:&nbsp;1</div> </div>
				$this->requirements = array();
				foreach ($child->children() as $child2) {
					$this->requirements[] =  trim(html_entity_decode((string)$child2));
				}
				break;
				//itemes
			case 'itemes': 
				$this->properties = array();
				foreach ($child->children() as $child2) {
					$this->properties[] =  trim(html_entity_decode((string)$child2));
				}
				break;
			case 'itemdescription': 
				//<div class="itemdescription">A short wooden shaft used to make tools.</div>
				$this->description = trim(html_entity_decode((string)$child));
				break;
			case 'itemdur':
				$c2 = $child->children();
				foreach ($c2[0]->children() as $child2) {
					$c3 = $child2->children();
					//	echo (string)$c3[0]['class'];
					switch ($c3[0]['class']) {

					case 'itemdurability':
						preg_match("/\D(\d+)\D*\/\D*(\d+)$/",(string)$c3,$m);
						$this->durability = $m[1]."/".$m[2];
						break;
					case 'itemsturdiness':
						$this->sturdiness = trim(html_entity_decode((string)$c3));
						break;
					}
				}
			case 'itemsw': 
					/*
					<div class="itemsw">
						<div class="itemstacksize">Stacks to&nbsp;50</div>
						<div class="itemworth">
									Worth&nbsp;&nbsp;&nbsp;1
									<img class="coin" src="http://content.level3.turbine.com/sites/lorebook.lotro.com/images/icons/currency/silver.gif" />
									&nbsp;80
									<img class="coin" src="http://content.level3.turbine.com/sites/lorebook.lotro.com/images/icons/currency/copper.gif" />
						</div>
					</div>
					 */

				foreach ($child->children() as $child2) {

					switch ($child2['class']) {
					case 'itemworth': 
						//preg_match("/Worth.*((\d+).*gold.gif)?.*((\d+).*silver.gif)?.*((\d+).*copper.gif)?/",(string)$child2,$m);
						preg_match("/(\d+)\D*(\d+)?\D*(\d+)?/",(string)$child2,$m);
						if (count($m)==4 && $m[2] != "" && $m[3] != "")
							$this->worth = $m[1].",".$m[2].",".$m[3];
						else if (count($m)==3 && $m[2] != "")
							$this->worth = "0,".$m[1].",".$m[2];
						else
							$this->worth = "0,0,".$m[1];
						break;
					case 'itemstacksize':
						preg_match("/\D(\d+)$/",(string)$child2,$m);
						$this->stackSize = $m[1];
						break;
					}
				}
				break;
			default:
				//case 'itemname common': //<div class="itemname common">Short Black Ash Shaft</div>
				if (preg_match("/^itemname (.*)$/", $child['class'], $m))
				{
					$this->itemname = (string)$child;
					$this->rareity = $m[1];
				}
				break;

			}

		}
	}

}

/**
 * Used to represent a character from the API. This is used both for the character sheets and the Guild listings.
 * This means that not all of the fields are always populated.
 */
class Character{
	/**
	 * The name of this character.
	 *
	 * @var string
	 */
	public $name;
	/**
	 * The name of the server that this character is on.
	 *
	 * @var string
	 */
	public $world;
	/**
	 * Indicates if this character is a monster. (Y=1/N=0)
	 *
	 * @var int
	 */
	public $monster;
	/** 
	 * The race that this character belongs to.
	 *
	 * @var string
	 */
	public $race;
	/**
	 * The class of this character (e.g. Champion, Warden etc)
	 *
	 * @var string
	 */
	public $cclass;
	/** 
	 * The level of this character.
	 *
	 * @var int
	 */
	public $level;
	/**
	 * The orgin of this character. eg. "The Iron Hills"
	 *
	 * @var string
	 */
	public $origin;
	/**
	 * The PvMP rank of this character
	 *
	 * @var int
	 */
	public $rank;

	/**
	 * The kin/tribe that this character belongs to.
	 *
	 * (Warning Guild is not fully populated if this class was intialised as part of a character sheet request).
	 *
	 * @var Guild
	 */
	public $guild;
	/**
	 * The crafting vocation of this character. Not populated for monster characters!
	 *
	 * (Warning: only populated if this class was intialised as part of a character sheet request).
	 * 
	 * @var Vocation
	 */
	public $vocation;
	/**
	 * PvMP info for this Character
	 *
	 * (Warning: only populated if this class was intialised as part of a character sheet request).
	 * 
	 * @var Pvmp
	 */ 
	public $pvmp;
	/**
	 * The statistics/characteristics of this character.
	 *
	 * (Warning: only populated if this class was intialised as part of a character sheet request).
	 * 
	 * @see Statistic
	 * @var array
	 */ 
	public $stats;
	/**
	 * The items this character has equiped.
	 *
	 * (Warning: only populated if this class was intialised as part of a character sheet request).
	 * 
	 * @see Item
	 * @var array
	 */ 
	public $items;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "character")
			throw new Exception('Expected character, found '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->name = $xml['name'];
		$this->world = $xml['world'];
		$this->monster = $xml['monster'];
		$this->race = $xml['race'];
		$this->cclass = $xml['class'];
		$this->level = $xml['level'];
		$this->origin = $xml['origin'];
		$this->rank = $xml['rank'];

		foreach ($xml->children() as $child) {
			switch ($child->getName()) {
			case 'guild':
				$this->guild = new Guild($child);
				break;
			case 'pvmp':
				$this->pvmp = new Pvmp($child);
				break;
			case 'vocation':
				$this->vocation = new Vocation($child);
				break;	
			case 'stats':
				$this->stats =  array();
				foreach ($child->children() as $stat) {
					if ($stat->getName() != "stat")
						throw new Exception('Expected stat, found '.$xml->getName(), ErrorCodes::getCode("Wrong element"));
					$statTemp = new Statistic($stat);
					$this->stats["".$statTemp->name] = $statTemp;
				}
				break;
			case 'equipment':
				$this->items =  array();
				foreach ($child->children() as $item) {
					if ($item->getName() != "item")
						throw new Exception('Expected item, found '.$xml->getName(), ErrorCodes::getCode("Wrong element"));
					$itemTemp = new Item($item);
					$this->items["".$itemTemp->slot] = $itemTemp;
				}
				break;
			}

		}
	}
}

/**
 * Used to represent a Guild element in the API. Not all of the elements in this class will be populated if
 * it is created as part of a character sheet call.
 */
class Guild {
	/**
	 * The name of this kin/tribe.
	 *
	 * @var string
	 */
	public $name;
	/** 
	 * The rank of the this kin/tribe.
	 *
	 * @var int
	 */
	public $rank;
	/**
	 * The type of this kin/tribe. 
	 *
	 * @var string
	 */
	public $type;
	/** 
	 * The server that this kin/tribe is on
	 *
	 * @var string
	 */
	public $world;
	/**
	 * The theme of this kin/tribe, e.g. "Mixed Kinship Theme"
	 *
	 * @var string
	 */
	public $theme;
	/** 
	 * The number of members in this kin/tribe
	 *
	 * @var int
	 */
	public $memberCount;

	/**
	 * The charaters in this kin/tribe
	 *
	 * @see Character
	 * @var array 
	 */
	public $characters;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "guild")
			throw new Exception('Expected guild, found a '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->name = $xml['name'];
		$this->rank = $xml['rank'];
		$this->type = $xml['type'];
		$this->world = $xml['world'];
		$this->theme = $xml['theme'];
		$this->memberCount = $xml['memberCount'];

		foreach ($xml->children() as $child) {
			switch ($child->getName()) {
			case 'characters':
				$this->characters = array();
				foreach ($child->children() as $character) {
					if ($character->getName() != "character")
						throw new Exception('Expected character, found '.$xml->getName(), ErrorCodes::getCode("Wrong element"));
					$charTemp = new Character($character);
					$this->characters["".$charTemp->name] = $charTemp;
				}
				break;
			}

		}
	}

	/**
	 * Checks to see if this is a Tribe or a Kin by inspecting the Guild Theme
	 *
	 * Tribes always have "Tribe" in the theme - e.g.: Mixed Tribe Theme, Ongburz Tribe Theme, Blogmal Tribe Theme, Tarkrip Tribe Theme
	 * 
	 * @return bool True if this is a kinship (not monster tribe).
	 * @since 0.2
	 * @author Chris Thomson
	 */
	public function isKinship()
	{
		return	(!(preg_match("/Tribe/",$this->theme)));
	}

}

/**
 * Used to represent the PvMP characteristics of a character from the API
 */
class Pvmp {
	/**
	 * The number of rating points
	 *
	 * @var int
	 */
	public $ratingPoints;
	/**
	 * The number of gloy points
	 *
	 * @var int
	 */
	public $gloryPoints;
	/** 
	 * The glory rank
	 *
	 * @var int
	 */
	public $gloryRank;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "pvmp")
			throw new Exception('Expected pvmp, found a '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->ratingPoints = $xml['ratingPoints'];
		$this->gloryPoints = $xml['gloryPoints'];
		$this->gloryRank = $xml['gloryRank'];
	}
}

/**
 * Used to represent the crafting vocation of a player character in the API.
 */
class Vocation {
	/**
	 * The name of this vocation, e.g. "Historian" "Explorer"
	 *
	 * @var string
	 */
	public $name;

	/**
	 * The individual professions that make up the crafting vocation.
	 *
	 * @see Profession
	 * @var array 
	 */
	public $professions;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "vocation")
			throw new Exception('Expected vocation, found a '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->name = $xml['name'];
		foreach ($xml->children() as $child) {
			switch ($child->getName()) {
			case 'professions':
				$this->professions = array();
				foreach ($child->children() as $profession) {
					if ($profession->getName() != "profession")
						throw new Exception('Expected profession, found '.$xml->getName(), ErrorCodes::getCode("Wrong element"));
					$profTemp = new Profession($profession);
					$this->professions["".$profTemp->name] = $profTemp;
					
				}
				break;
			}

		}
	}
}

/**
 * Used to store the crafting Profession of a player character.
 */
class Profession {
	/**
	 * The name of this profession
	 *
	 * @var string
	 */
	public $name;
	/**
	 * The proficiency level of this character in this profession. (1-6)
	 *
	 * @var int
	 */
	public $proficiency;
	/**
	 * The mastery level of this character in this profession. (1-6)
	 *
	 * @var int
	 */
	public $mastery;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "profession")
			throw new Exception('Expected profession, found a '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->name = $xml['name'];
		$this->proficiency = $xml['proficiency'];
		$this->mastery = $xml['mastery'];
	}

	/**
	 * Turns a crafting level into the names used in the game (English only)
	 *
	 * @param int $level the level to turn into a string
	 * @return string
	 * @since 0.2
	 * @author Chris Thomson
	 */
	private function intToLevelString($level)
	{
		switch ($level) { 
		case 1: return "Apprentice"; break; 
		case 2: return "Journeyman"; break; 
		case 3: return "Expert"; break; 
		case 4: return "Artisan"; break; 
		case 5: return "Master"; break; 
		case 6: return "Supreme"; break;
		}
	}	

	/**
	 * Returns the proficiency level of this chracter in this profession as a string.
	 *
	 * @return string e.g. "Master" "Expert"
	 * @since 0.2
	 * @author Chris Thomson
	 */
	function getProficiency()
	{
		return intToLevelString($this->proficiency);
	}

	/**
	 * Returns the mastery level of this chracter in this profession as a string.
	 *
	 * @return string e.g. "Master" "Expert"
	 * @since 0.2
	 * @author Chris Thomson
	 */
	function getMastery()
	{
		return intToLevelString($this->mastery);
	}

}

/**
 * Represents a player statistic such as morale level.
 */
class Statistic {
	/**
	 * The name of the statistic
	 *
	 * @var string
	 */
	public $name;
	/**
	 * The value of the statistic
	 *
	 * @var int
	 */
	public $value;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "stat")
			throw new Exception('Expected stat, found a '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->name = $xml['name'];
		$this->value = $xml['value'];
	}
}

/**
 * Represents an item summary as returned from an API request. To get the item details use getLorebookItem
 *
 * @see getLorebookItem
 */
class Item 	{
	/**
	 * The name of this item
	 *
	 * @var string
	 */
	public $name;
	/**
	 * The slot that this item can be equipped in.
	 *
	 * @var string
	 */
	public $slot;
	/**
	 * A URL to the lorebook entry page for this item
	 *
	 * @var string
	 */
	public $lorebookEntry;

	/**
	 * The full description of this item fetched from the Lorebook. Private as it takes a few seconds to get this 
	 * data so we only do it on request.
	 *
	 * @var LorebookItem
	 */
	private $lorebookData;
	/**
	 * The id of this item in the Lorebook.
	 *
	 * @var int
	 */
	public $id;

	/**
	 * Initalises this class from the appropriate XML
	 *
	 * @param SimpleXMLElement $xml
	 */
	function __construct(SimpleXMLElement $xml) {
		if ($xml->getName() == "error")
			throw new Exception($xml['message'], ErrorCodes::getCode($xml['code']));
		if ($xml->getName() != "item")
			throw new Exception('Expected item, found a '.$xml->getName(), ErrorCodes::getCode("Wrong element"));

		$this->name = htmlentities(utf8_decode($xml['name']));
		$this->slot = $xml['slot'];
		$this->lorebookEntry = $xml['lorebookEntry'];

		preg_match("/id=(\d+)$/",$this->lorebookEntry,$m);
		$this->id = $m[1];


	}

	/**
	 * Get the URL of the icon for this item.
	 *
	 * @return string the URL.
	 * @since 0.2
	 * @author Chris Thomson
	 */
	public function getIconURL()
	{
		return "http://loreimages.level3.lotro.com/icon.php?id=$this->id&type=item";
	}

	/**
	 * Get the LorebookItem for this item which has all the details in it.
	 *
	 * @return LorebookItem
	 * @since 0.2
	 * @author Chris Thomson
	 * @throws Exception
	 */
	public function getLorebookItem()
	{
		if ($lorebookData == null)
		{	
			$api = new Api();
			$this->lorebookData = $api->getItemInformation($this->id); 
		}
		return $lorebookData;
	}
}


?>
